讓我們從複習一下InheritedWidget開始,這是我能做到的最簡單的InheritedWidget範例。可以看到,除了我們必須改為繼承長得跟StatelessWidget/StatefulWidget很不一樣的InheritedWidget,還有必須呼叫名字長得可笑的dependOnInheritedWidgetOfExactType之外,其實看起來還不算太糟對嗎?
不過,通常我們想傳遞的是會變動的State,但InheritedWidget卻不是StatefulWidget,所以事情就開始有趣起來了。我們必須用一個StatefulWidget來管理狀態,進行更新,把狀態傳遞給InheritedWidget,再由InheritedWidget提供給子樹上的Widget。
我可以理解Flutter團隊應該是為了單一責任原則(Single Responsibility Principle)而選擇這樣設計,讓InheritedWidget唯一的責任,就是把變數提供給子樹上的Widget。不過我認為Flutter團隊沒有想清楚的是,我們會想傳遞下去的變數,絕大多數就是我們提昇上來的狀態,也就須要變動和更新。
因為我們的主角還是Riverpod,我就不一一示範InheritedWidget的其它問題了,簡單來說:
關於5.可能須要額外說明一下。除了Widget會不斷複合之外,我們的整個程式還有很多可能發生複合的地方,例如BloC/ViewModel, Interactor/UseCase, Service...如果今天我們想在這些地方穿越多層暴露狀態,顯然就不能使用InheritedWidget。但是如果我們找到其它解決方案,可以在任何型別的樹中自由地暴露狀態,那我們還須要InheritedWidget嗎?
好,回來看看Provider能解決多少問題吧,把上一個範例改成使用Provider看看。首先程式碼絕對大有改善,不只是行數減少而已,邏輯也清楚很多。那麼嵌套的問題呢?當然就是靠MultiProvider。不僅如此,Provider還免費提供了lazy-loading,當你想透過MultiProvider再最上層提供各種昂貴的service時,肯定會須要它。最後就是最近統一起來的context.watch
, context.read
, context.select
,透過簡單且一致的API,提供了InheritedWidget難以實現的各種變數取用功能。
當然Provider也不是完美的。雖然解決了問題1.和2.,再加上一些額外功能,已經是非常大的進步,也因此它才會在社群裡如此火紅。但畢竟它背後還是依賴InheritedWidget,問題3.、4.、5.可以說是完全沒變。再加上隨著Provider的火紅,人們對於它的要求也越來越多了,於是現在我們有這些問題:
顯然3,4,5是不可能解決的,只要Provider還是個widget,還是依賴InheritedWidget的話。因此,Provider的作者Rémi Rousselet決定,全部重零開始吧!打造一個不依賴InheritedWidget,甚至不依賴Flutter的解決方案。於是,終於,萬眾矚目的Riverpod出現了!
馬上來看看它表現如何吧。首先整個程式邏輯又更簡潔了一點,再來因為Riverpod裡的Provider
已經不再是Widget了,不需要塞進widget tree,自然就沒有嵌套的問題。也就是說一開始Provider所解決的,InheritedWidget的問題1和2,再這裡也更進步了。那麼Provider繼承自InheritedWidget的問題3,4,5和新增的6,7,8呢?因為我們現在是直接透過變數來存取Provider
,而不是透過型別,問題3和4就自動消失了,同時8也可以簡單實現。接下來我們繼續看看剩下的5,6,7:
至於5,我們就來看看怎麼在非Flutter環境使用Riverpod吧。這裡的ProviderContainer作用就跟上個範例裡的ProviderScope一樣,用來存放我們所有的state。我們可以看到它其實有點像Observer模式了,我相信它在純Dart環境能做得到的所有事,用stream/rxDart都能做到,但至少它跟Provider(package)比起來,就多了很多可以發揮的地方。這裡作者將Riverpod拆成了riverpod
, flutter_riverpod
, hooks_riverpod
三個套件,如果我們想在一個純Dart模組使用(ex. Domain),只需要加入riverpod
就好了。
接著來看看問題6怎麼解決:
final cityProvider = Provider((ref) => 'London');
final weatherProvider = FutureProvider((ref) async {
final city = ref.watch(cityProvider);
return fetchWeather(city: city);
});
就這樣,我都不用開DartPad了,也不須要使用什麼特別的ProxyProvider23456,簡單明瞭。
至於問題7,那就是靠autoDispose了:
final userProvider = StreamProvider.autoDispose<User>((ref) { .... })
任何種類的Provider都搭配autoDispose使用,只要沒有人在watch就會自動被dispose掉。
到這裡我們列出的所有問題都解決了!當然除了這些之外,Riverpod還有提供了許多Provider沒有的功能,但這篇文章畢竟不是Riverpod教學文,就不一一介紹了。我們主要目的還是在於,確認Riverpod真的有解決了從InheritedWidget到Provider所無法解決的問題。如果這些的確是你曾經遇過,或你能夠預想未來可能會遇到的問題,Riverpod就很值得你嘗試看看。